#include <fmt/format.h>
#include "../opengl/opengl.hpp"
#include "../gl/Camera.hpp"
#include "../gl/Sphere.hpp"
#include "../gl/Window.hpp"
#include "../Renderer/Camera3D.hpp"
#include "../Renderer/Object3D.hpp"

namespace gl = opengl;

struct AxisHelper {
    static constexpr auto vs_src =
        "#version 450 core\n"
        "layout(location=0)in vec3 pos;layout(location=0)out vec4 color;"
        "struct World{vec3 cameraPos;mat4 matVP;};layout(binding=0,std140)uniform WorldBlock{World world;};"
        "void main(){gl_Position=world.matVP*vec4(pos,1.0);const vec3 colors[]={vec3(1,0,0),vec3(0,1,0),vec3(0,0,1)};"
        "color=vec4(colors[gl_VertexID>>1],1.0);}";
    static constexpr auto fs_src =
        "#version 450 core\n"
        "layout(location=0)in vec4 color;layout(location=0)out vec4 fragColor;void main(){fragColor=color;}";

    struct {
        glm::vec3 x1{0.0}, x2{0.0}; // Red
        glm::vec3 y1{0.0}, y2{0.0}; // Green
        glm::vec3 z1{0.0}, z2{0.0}; // Blue
    } data;
    gl::buffer vertices;
    gl::vertex_array vao;
    gl::program program;

    AxisHelper(float size = 1.0f)
        : vertices(sizeof(data)), vao({gl::vertex_attribute(0, gl::type_f32, 3, 0)}),
          program({gl::shader(gl::shader::vertex_shader, vs_src), gl::shader(gl::shader::fragment_shader, fs_src)}) {
        auto half = size / 2.0;
        data.x1.x = -half;
        data.x2.x = half;
        data.y1.y = -half;
        data.y2.y = half;
        data.z1.z = -half;
        data.z2.z = half;
        vertices.set_data(&data, sizeof(data));
    }
    void render() const {
        vao.bind();
        program.use();
        vao.set_vertex_buffer(vertices, sizeof(glm::vec3));
        gl::api::glDrawArrays(gl::mode_lines, 0, 6);
    }
};

int main() {
    Object3D sphere;
    Window window("Model", 800, 600);
    gl::init_opengl(gl::opengl_config{.load_function = &SDL_GL_GetProcAddress,
                                      .debug_msg_callback = [](auto, auto, auto id, auto, auto, auto msg, auto) {
                                          fmt::println("[OpenGL Object {}] {}", id, msg);
                                      }});
    gl::vertex_array vao({
        gl::vertex_attribute(0, gl::type_f32, 3, 0),  // position
        gl::vertex_attribute(1, gl::type_f32, 3, 12), // normal
    });
    Sphere<64> mesh(1.0f);
    gl::buffer vertices(sizeof(mesh.vertices), mesh.vertices);
    gl::buffer indices(sizeof(mesh.indices), mesh.indices);
    gl::program program({gl::shader::from_file(gl::shader::vertex_shader, "../shader/lightning/vs.glsl"),
                         gl::shader::from_file(gl::shader::fragment_shader, "../shader/lightning/fs.glsl")});
    fmt::println("{}", program.get_error());


    PerspectiveCamera camera;
    camera.set_position({4.f, 4.f, -4.f});
    camera.look_at({0.f, 0.f, 0.f});

    window.on_exec = [&](auto) {
        auto [w, h] = window.get_size();
        gl::api::glViewport(0, 0, w, h);
        // camera.setAspectRatio((float) w / (float) h);
        camera.set_aspect_ratio((float) w / (float) h);
    };
    window.on_resize = [&](auto, int w, int h) {
        gl::api::glViewport(0, 0, w, h);
        // camera.setAspectRatio((float) w / (float) h);
        camera.set_aspect_ratio((float) w / (float) h);
    };

    gl::buffer worldUBO(80);
    worldUBO.bind_to(gl::buffer::uniform_buffer, 0);
    struct Transform {
        glm::mat4 modelMatrix;
        glm::mat4 mvpMatrix;
        glm::mat4 normalMatrix;
    } transform;
    struct Material {
        glm::vec3 color{1.0f};
        float shiness = 16.0f;
    } material;
    struct Light {
        glm::vec3 ambient{0.1f, 0.1f, 0.1f};
        glm::vec3 diffuse{0.6f, 0.6f, 0.6f};
        glm::vec3 specular{0.3f, 0.3f, 0.3f};
        glm::vec3 position{0.0f, 10.0f, 0.0f};
    } light;

    AxisHelper helper(5.0);
    window.on_render = [&](auto) {
        float t = SDL_GetTicks() / 500.0f;
        sphere.setPosition(std::cos(t), 0.0f, std::sin(t));
        sphere.updateMatrix();

        gl::clear_buffers();
        helper.render();
        vao.bind();
        program.use();

        camera.update_matrix();
        worldUBO.set_data(glm::value_ptr(camera.position), sizeof(glm::vec3));
        worldUBO.set_data(glm::value_ptr(camera.viewProjectionMatrix), sizeof(glm::mat4), 16);
        program.set_mat4f(3, glm::value_ptr(camera.viewProjectionMatrix * sphere.worldMatrix()));

        // camera.updateMatrix();
        // camera.updateCameraMatrix();
        // worldUBO.set_data(glm::value_ptr(camera.position()), sizeof(glm::vec3));
        // worldUBO.set_data(glm::value_ptr(camera.viewProjectionMatrix()), sizeof(glm::mat4), 16);
        // program.set_mat4f(3, glm::value_ptr(camera.viewProjectionMatrix() * sphere.worldMatrix()));

        // Material
        program.set_vec3f(5, glm::value_ptr(material.color));
        program.set_float32(6, material.shiness);
        // Light
        program.set_vec3f(7, glm::value_ptr(light.ambient));
        program.set_vec3f(8, glm::value_ptr(light.diffuse));
        program.set_vec3f(9, glm::value_ptr(light.specular));
        program.set_vec3f(10, glm::value_ptr(light.position));
        vao.set_vertex_buffer(vertices, sizeof(Sphere<64>::vertex));
        vao.set_index_buffer(indices);
        gl::api::glDrawElements(gl::mode_triangles, Sphere<64>::num_indices, gl::type_u32, nullptr);
    };
    window.on_process_event = [&](auto, const SDL_Event *event) {
        if (event->type == SDL_EventType::SDL_MOUSEWHEEL) {
            constexpr float sensitivity = 2.0f;
            // auto fov = glm::clamp(camera.fov() - sensitivity * event->wheel.preciseY, 15.0f, 85.0f);
            // camera.setFov(fov);
            auto fov = glm::clamp(camera.fov - sensitivity * event->wheel.preciseY, 15.0f, 85.0f);
            camera.set_fov(fov);
        }
    };

    return window.exec();
}